Citi Bike Data
Welcome to the off platform project focused on visualization. In this
project, we will be exploring data associated with the New York City
bike share program, Citi Bike! Remember, it may be easiest to read these
instructions by clicking on the “Preview” button in RStudio.
There are over 850 Citi Bike stations in New York City — users check
a bike out from a starting station and then dock that bike at a
different station when they reach their destination. Citi Bike offers a
variety of memberships, but most memberships allow for trips between 30
and 45 minutes — this will be relevant once we start digging into the
dataset.
Citi Bike publically releases a variety of datasets. We’ve included a
dataset containing information about individual trips from January of
2020. If you’d like to download a more recent dataset, or investigate
other data that Citi Bike provides, take a look at their System Data page.
There are so many interesting questions that you can investigate with
this data — we’re about to walk you through a few, but we’d love to see
what else you can discover!
Investigate the Data
We’ve included a file named january_trips.csv. Load this
data into a dataframe using the read.csv() function. Note
that this dataset is big. It may take a few minutes to load —
if you’d like to use a subset of this data that will take less time to
load, we’ve included a file named january_trips_subset.csv
as well. We strongly recommend using this subset of the data. When
loading these datasets, make sure that the .csv files are in the same
directory as this .Rmd file.
Note that because this dataset is so large, it may take several
seconds to load the data or preview your R Notebook.
# Load the data set into a data frame
all_data <- read.csv("january_trips_subset.csv")
Now that we’ve loaded the dataset, the easiest way to investigate the
data is to click on the variable name in the “Global Environment” tab in
RStudio. This will let you scroll through the data as if it were a
spreadsheet. If you want to display some of the data in this document,
call the head() function using your data frame as a
parameter. Make sure to scroll through all of the columns!
# Investigate the data
head(all_data)
Since we have information about the starting and ending location for
each trip, let’s quickly make a heat map of the starting locations. Make
a heat map using ggplot() and geom_bin2d(). If
you make the bin width for each axis very small (we used
0.001), you should see the shape of Manhattan, Brooklyn,
and Queens! Check out the rectangle in Manhattan with no stations —
that’s Central Park!
Make sure to install and load ggplot2 and
dplyr!
# Install and load ggplot2 and dplyr
library(ggplot2)
library(dplyr)
# Create a heatmap
citibike_heatmap <- all_data %>%
ggplot(
aes(
x = start.station.longitude,
y = start.station.latitude
)
) +
geom_bin2d(
binwidth = c(0.001, 0.001)
)
citibike_heatmap

We also have the duration of each trip. Using these features, we can
calculate the average speed of each trip.
Finally, since we also have the date of birth for each rider, we can
calculate their age.
Let’s work towards building a line graph where age is on the x axis
and average speed is on the y axis. This graph could help Citi Bike
understand how their users are using their bikes. For example, if
younger riders tend to bike much faster than older riders, Citi Bike may
want to think about ways to encourage younger riders to bike more
cautiously.
Before we begin to work on the visualization, we’ll have to work a
bit with our dataset to get all of the relevant columns.
Modifying the Data Frame: Subset and Age
Since this dataset is so big, we recommend using the
filter() function to grab a subset of the data. For
example, you could grab only the rows where the duration was under 900
seconds (15 minutes). We stored these rows in a new data frame named
short_trips. Note that we did this only to speed up the
runtime of upcoming operations. This is completely optional — if you’re
happy to wait a bit for each operation, feel free to use the complete
data set.
# Create a subset of the data
short_trips <- all_data %>%
filter(tripduration < 900)
head(short_trips)
Next, let’s add a column called age to the data frame.
age should be 2020 minus birth.year (this data
was collected in 2020). Use the mutate() function to do
this. After calling mutate, make sure to save the result in a variable.
We should save the result back to short_trips.
# Add the age column
short_trips <- short_trips %>%
mutate(
age = 2020 - birth.year
)
head(short_trips)
Modifying the Data Frame: Distance
In order to calculate the speed of each biker, we need to find the
total distance they traveled. Luckily, we have information about the
starting and ending latitudes and longitudes. Let’s use those four
columns to create a new column named distance.
There are many different ways to calculate distance. We’ll walk you
through the strategy we used. However, before following along with us,
challenge yourself to solve this problem on your own — one of the goals
of these off platform projects is to get comfortable problem solving on
your own. Try to use Google to find the packages you might need to
calculate the distance between latitude and longitude coordinates. Use
the code block below to try solving this problem on your own. We’ll walk
you through our solution in the following section.
As you write your code that edits the data frame, consider printing
the head of the data frame to validate the work you are doing!
# Try creating a distance column in your data frame here:
There are many different strategies to calculate the distance between
two points. The simplest way to do this would be to find the length of
the straight line between the two points. This is a massive assumption
to make — it would be remarkable if any of these bike trips traveled in
a straight line between the two points without making any turns or
curves.
That being said, finding the straight line distance is a good
starting point. The distHaversine() function found in the
geosphere library can calculate this distance.
First, install and load the geosphere library.
Next, use dplyr’s select() function to
create two new data frames that contain only the latitudes and
longitudes of the starting and ending points. We called these data
frames starting_stations and
ending_stations.
Finally, use dplyr’s mutate() function to
add a column named distance to your data.
distance should be calculated by calling
distHaversine() using starting_stations and
ending_stations as parameters.
If you get stuck, use ?distHaversine to check the
documentation to see more examples! You can also use the documentation
to find the units of the result of distHaversine()!
# Use the geosphere library to create a distance column
# install.packages("geosphere")
library(geosphere)
starting_stations <- short_trips %>%
select(start.station.latitude, start.station.longitude)
ending_stations <- short_trips %>%
select(end.station.latitude, end.station.longitude)
short_trips <- short_trips %>%
mutate(
distance = distHaversine(starting_stations, ending_stations)
)
head(short_trips)
Modifying the Data Frame: Speed
Now that we’ve made a column containing the distance of each trip,
let’s make another column containing the average speed of each trip.
This column should be easier to create than the previous — speed can be
calculated by dividing the distance column by the
tripduration column. This will give us the average speed in
meters per second. Use the mutate() function to make the
speed column!
# Create the speed column
short_trips <- short_trips %>%
mutate(
speed = distance / tripduration
)
head(short_trips)
Modifying the Data Frame: Average Speed by Age
We’re almost there! Now that we have the speed of every bike trip, we
want to group those trips by age and find the average speed
of each age.
Do this by piping your data frame into the group_by()
function using age as a parameter.
Then pipe the result of that function into the
summarize() function. summarize() works
similarly to mutate() — pass
mean_speed = mean(speed) to the summarize()
function to create a new column named mean_speed. Save this
new data frame in a variable called
average_speed_by_age.
# Use group_by() and summarize() to get the mean speed of each age
average_speed_by_age <- short_trips %>%
group_by(age) %>%
summarise(mean_speed = mean(speed))
head(average_speed_by_age)
Visualization!
We made it! We now have the average speed of every age in our
dataset. Let’s use ggplot2 to make a line graph to see if
younger people really do bike faster. Make sure to install and load
ggplot2 if you haven’t done so already. Pass your data
frame to ggplot() and add a geom_line().
geom_line() should contain an aesthetic where
x = age and y = mean_speed.
# Install and load ggplot2 to create a line graph of age and mean speed
average_speed_by_age %>%
ggplot(
aes(
x = age,
y = mean_speed
)
) +
geom_line()

Nice work! Our intuition seems to be right — there’s a steady drop in
speed until we hit some outliers. It would be pretty surprising to see
someone over the age of 100 using a bike share program! Let’s filter the
data to only show ages less than 80 and redraw our visualization.
# Filter the data frame to only contain rows where the age is less than 80
average_speed_by_age <- average_speed_by_age %>%
filter(age < 80)
average_speed_by_age %>%
ggplot(
aes(
x = age,
y = mean_speed
)
) +
geom_line()

That looks a bit better! Let’s do some work to make our graph look a
bit more professional. Add a title and axis labels. We also centered our
title!
# Add a title and label the axes
average_speed_by_age %>%
ggplot(
aes(
x = age,
y = mean_speed
)
) +
geom_line() +
labs(
title = "Average Speed of Citi Bike users by age (January 2020)",
x = "Age",
y = "Mean Speed (m/s)"
) +
theme(plot.title = element_text(hjust = 0.5))

Filtering By Gender
Great work! This visualization gives us some great insights on how
Citi Bike users are using their bikes. Let’s dive even deeper! We can
group our data by more than one feature.
Find your line of code that grouped our data by age.
Copy it, but add gender as a parameter to the
group_by() function. Save the result in a data frame named
average_speed_by_age_and_gender. Inspect this data frame to
see what it contains.
# Use group_by() again to group by both age and gender
average_speed_by_age_and_gender <- short_trips %>%
group_by(age, gender) %>%
summarise(mean_speed = mean(speed))
`summarise()` has grouped output by 'age'. You can override using the `.groups` argument.
average_speed_by_age_and_gender
Let’s now visualize the difference in average speed by age
and gender. Note that if you look in the documentation for the
data, a 0 represents a user that didn’t specify their
gender as male or female, a 1 represents a user identifying
as male, and a 2 represents a user identifying as
female.
The previous call to ggplot() and
geom_line() should be close to what we want. Add the
parameter color = gender to the aesthetic in
geom_line(). Make sure you use the new data containing the
gender information! You once again may want to filter out the ages
greater than 80.
Note that this graph won’t quite be what we want yet, but we’re
getting close!
# Make a line graph of your new filtered data frame
average_speed_by_age_and_gender <- average_speed_by_age_and_gender %>%
filter(age < 80)
average_speed_by_age_and_gender %>%
ggplot(
aes(
x = age,
y = mean_speed,
color = gender
)
) +
geom_line() +
labs(
title = "Average Speed of Citi Bike users by age (January 2020)",
x = "Age",
y = "Mean Speed (m/s)"
) +
theme(plot.title = element_text(hjust = 0.5))

It’s a bit hard to tell what is happening in that graph — but one
oddity that sticks out is the scale used for gender. We know that our
gender data is represented as three distinct values — 0,
1, and 2. However, ggplot() is
using gender as a continuous variable.
We can turn this column into a factor by using the
as.factor() and mutate() functions. Pipe your
data frame into the mutate() function and use
gender = as.factor(gender) as the parameter.
Note that when you make this column a factor, you will see a number
of warnings. This warning is telling you that the type of the values in
the gender column have been changed from integers to characters.
Then redraw your graph.
# Use mutate() and as.factor() to change the gender column into a factor.
average_speed_by_age_and_gender <- average_speed_by_age_and_gender %>%
mutate(
gender = as.factor(gender)
)
average_speed_by_age_and_gender %>%
ggplot(
aes(
x = age,
y = mean_speed,
color = gender
)
) +
geom_line() +
labs(
title = "Average Speed of Citi Bike users by age (January 2020)",
x = "Age",
y = "Mean Speed (m/s)"
) +
theme(plot.title = element_text(hjust = 0.5))

Nice work! We can now see the average speeds by age broken into the 3
genders Citi Bike accounts for. You can see that male-identifying users
typically bike faster than female-identifying users. Users with an
unknown gender don’t follow a specific pattern — it is likely that there
isn’t enough data to properly visualize those users.
For our final version of this graph, we filtered out the users with a
gender of 0, and we labeled the lines as “Male Identifying”
and “Female Identifying” using the scale_color_discrete()
function. Take a look at the documentation for this function using
?scale_color_discrete to change the label of each line.
# Filter the data frame to only include genders 1 and 2. Set appropriate labels for the legend
average_speed_by_age_and_gender <- average_speed_by_age_and_gender %>%
filter(
gender == 1 | gender == 2
)
average_speed_by_age_and_gender %>%
ggplot(
aes(
x = age,
y = mean_speed,
color = gender
)
) +
geom_line() +
labs(
title = "Average Speed of Citi Bike users by age (January 2020)",
x = "Age",
y = "Mean Speed (m/s)"
) +
theme(plot.title = element_text(hjust = 0.5)) +
scale_color_discrete(
name = "Gender",
labels = c(
"1" = "Male",
"2" = "Female"
)
)

Making a Stacked Bar Plot Of Ages
Let’s make one final graph. For this graph, we’re interested in
seeing the distribution of Citi Bike users’ age and gender. Let’s use a
stacked bar plot to do this. We’ll want to create a bar plot where age
is on the x axis, count is on the y axis, and each bar is split into
genders.
Let’s start by using our short_trips dataset. We’ll want
to call group_by() using this dataset and pipe the result
to tally(). This will let us get a count of the number of
bikers for each age and gender. We saved this new data frame in a
variable called age_counts.
# Create the age_counts data frame
age_counts <- short_trips %>%
group_by(
age, gender
) %>%
tally()
head(age_counts)
If you look at the head of this new data frame, you’ll see the counts
are stored in a column named n. Let’s now use
ggplot() and geom_col() to create a stacked
bar plot. ggplot() should have an aes() where
x = age, y = n and
fill = gender.
# Create the stacked bar plot
age_counts %>%
ggplot(
aes(
x = age,
y = n,
fill = gender
)
) +
geom_col()

Great! There are some tweaks that we might want to make to this
graph. First, gender right now is represented as an integer. It will
make more sense if that column is represented as a factor. To do this,
we can pass as.factor(gender) as the value for the x
axis.
Next, it looks like we have some unusual data around the age of 50.
It looks like there are a ton of bikers with an unknown gender at that
age. This might be something we want to dig into a bit more, but for
now, let’s filter out the bikers with a gender of 0. We
also filtered out bikers with an age over 100 — that seems
like an error in data collection as well.
Finally, we labeled and titled our graph using labs()
and scale_fill_discrete()
# Filter and label your graph
age_counts <- age_counts %>%
filter(
age < 80, gender == 1 | gender == 2
)
age_counts %>%
ggplot(
aes(
x = age,
y = n,
fill = as.factor(gender)
)
) +
geom_col() +
labs(
title = "Average Speed of Citi Bike users by age (January 2020)",
x = "Age",
y = "Mean Speed (m/s)"
) +
theme(plot.title = element_text(hjust = 0.5)) +
scale_fill_discrete(
name = "Gender",
labels = c(
"1" = "Male",
"2" = "Female"
)
)

Further Work
Great work! You’ve made several graphs that show a real difference in
the way different groups of Citi Bike users bike. This could be a
valuable asset in helping Citi Bike understand how to make bike riding
safer in New York. However, there is so much more you can do with this
data!
To begin, there are some major flaws in the way we calculated the
speed. Specifically, we made some huge assumptions when
calculating the distance of each bike ride. Instead of calculating the
straight line distance using the geosphere library, we could take
advantage of a service like Google Map’s API to get a more accurate
measurement of distance. If you’re interested in look more into this
problem, investigate getting a Google
Maps API key and using a library like gmapsdistance.
Another great way to extend this project is to investigate other data
that Citi Bike makes available. We used data found in the Citi Bike
Trip Histories section on the System Data page. On
the System Data page, you can find different dataset, including
information about membership data and real time station data. You could
use this real time data to track how the flow of bikes changes over the
course of the day. You could investigate how the weather impacts
membership. We would love to see any graphs or insights you produce!
LS0tCnRpdGxlOiAiRXhwbG9yZSBDaXRpIEJpa2UgRGF0YSIKb3V0cHV0OiBodG1sX25vdGVib29rCi0tLQoKIyMjIENpdGkgQmlrZSBEYXRhCgpXZWxjb21lIHRvIHRoZSBvZmYgcGxhdGZvcm0gcHJvamVjdCBmb2N1c2VkIG9uIHZpc3VhbGl6YXRpb24uIEluIHRoaXMgcHJvamVjdCwgd2Ugd2lsbCBiZSBleHBsb3JpbmcgZGF0YSBhc3NvY2lhdGVkIHdpdGggdGhlIE5ldyBZb3JrIENpdHkgYmlrZSBzaGFyZSBwcm9ncmFtLCBDaXRpIEJpa2UhIFJlbWVtYmVyLCBpdCBtYXkgYmUgZWFzaWVzdCB0byByZWFkIHRoZXNlIGluc3RydWN0aW9ucyBieSBjbGlja2luZyBvbiB0aGUgIlByZXZpZXciIGJ1dHRvbiBpbiBSU3R1ZGlvLgoKVGhlcmUgYXJlIG92ZXIgODUwIENpdGkgQmlrZSBzdGF0aW9ucyBpbiBOZXcgWW9yayBDaXR5ICZtZGFzaDsgdXNlcnMgY2hlY2sgYSBiaWtlIG91dCBmcm9tIGEgc3RhcnRpbmcgc3RhdGlvbiBhbmQgdGhlbiBkb2NrIHRoYXQgYmlrZSBhdCBhIGRpZmZlcmVudCBzdGF0aW9uIHdoZW4gdGhleSByZWFjaCB0aGVpciBkZXN0aW5hdGlvbi4gQ2l0aSBCaWtlIG9mZmVycyBhIHZhcmlldHkgb2YgbWVtYmVyc2hpcHMsIGJ1dCBtb3N0IG1lbWJlcnNoaXBzIGFsbG93IGZvciB0cmlwcyBiZXR3ZWVuIDMwIGFuZCA0NSBtaW51dGVzICZtZGFzaDsgdGhpcyB3aWxsIGJlIHJlbGV2YW50IG9uY2Ugd2Ugc3RhcnQgZGlnZ2luZyBpbnRvIHRoZSBkYXRhc2V0LgoKQ2l0aSBCaWtlIHB1YmxpY2FsbHkgcmVsZWFzZXMgYSB2YXJpZXR5IG9mIGRhdGFzZXRzLiBXZSd2ZSBpbmNsdWRlZCBhIGRhdGFzZXQgY29udGFpbmluZyBpbmZvcm1hdGlvbiBhYm91dCBpbmRpdmlkdWFsIHRyaXBzIGZyb20gSmFudWFyeSBvZiAyMDIwLiBJZiB5b3UnZCBsaWtlIHRvIGRvd25sb2FkIGEgbW9yZSByZWNlbnQgZGF0YXNldCwgb3IgaW52ZXN0aWdhdGUgb3RoZXIgZGF0YSB0aGF0IENpdGkgQmlrZSBwcm92aWRlcywgdGFrZSBhIGxvb2sgYXQgdGhlaXIgW1N5c3RlbSBEYXRhXShodHRwczovL3d3dy5jaXRpYmlrZW55Yy5jb20vc3lzdGVtLWRhdGEpIHBhZ2UuIFRoZXJlIGFyZSBzbyBtYW55IGludGVyZXN0aW5nIHF1ZXN0aW9ucyB0aGF0IHlvdSBjYW4gaW52ZXN0aWdhdGUgd2l0aCB0aGlzIGRhdGEgJm1kYXNoOyB3ZSdyZSBhYm91dCB0byB3YWxrIHlvdSB0aHJvdWdoIGEgZmV3LCBidXQgd2UnZCBsb3ZlIHRvIHNlZSB3aGF0IGVsc2UgeW91IGNhbiBkaXNjb3ZlciEKCiMjIyBJbnZlc3RpZ2F0ZSB0aGUgRGF0YQoKV2UndmUgaW5jbHVkZWQgYSBmaWxlIG5hbWVkIGBqYW51YXJ5X3RyaXBzLmNzdmAuIExvYWQgdGhpcyBkYXRhIGludG8gYSBkYXRhZnJhbWUgdXNpbmcgdGhlIGByZWFkLmNzdigpYCBmdW5jdGlvbi4gTm90ZSB0aGF0IHRoaXMgZGF0YXNldCBpcyBfYmlnXy4gSXQgbWF5IHRha2UgYSBmZXcgbWludXRlcyB0byBsb2FkICZtZGFzaDsgaWYgeW91J2QgbGlrZSB0byB1c2UgYSBzdWJzZXQgb2YgdGhpcyBkYXRhIHRoYXQgd2lsbCB0YWtlIGxlc3MgdGltZSB0byBsb2FkLCB3ZSd2ZSBpbmNsdWRlZCBhIGZpbGUgbmFtZWQgYGphbnVhcnlfdHJpcHNfc3Vic2V0LmNzdmAgYXMgd2VsbC4gV2Ugc3Ryb25nbHkgcmVjb21tZW5kIHVzaW5nIHRoaXMgc3Vic2V0IG9mIHRoZSBkYXRhLiBXaGVuIGxvYWRpbmcgdGhlc2UgZGF0YXNldHMsIG1ha2Ugc3VyZSB0aGF0IHRoZSAuY3N2IGZpbGVzIGFyZSBpbiB0aGUgc2FtZSBkaXJlY3RvcnkgYXMgdGhpcyAuUm1kIGZpbGUuCgpOb3RlIHRoYXQgYmVjYXVzZSB0aGlzIGRhdGFzZXQgaXMgc28gbGFyZ2UsIGl0IG1heSB0YWtlIHNldmVyYWwgc2Vjb25kcyB0byBsb2FkIHRoZSBkYXRhIG9yIHByZXZpZXcgeW91ciBSIE5vdGVib29rLgoKYGBge3J9CiMgTG9hZCB0aGUgZGF0YSBzZXQgaW50byBhIGRhdGEgZnJhbWUKCmFsbF9kYXRhIDwtIHJlYWQuY3N2KCJqYW51YXJ5X3RyaXBzX3N1YnNldC5jc3YiKQpgYGAKCk5vdyB0aGF0IHdlJ3ZlIGxvYWRlZCB0aGUgZGF0YXNldCwgdGhlIGVhc2llc3Qgd2F5IHRvIGludmVzdGlnYXRlIHRoZSBkYXRhIGlzIHRvIGNsaWNrIG9uIHRoZSB2YXJpYWJsZSBuYW1lIGluIHRoZSAiR2xvYmFsIEVudmlyb25tZW50IiB0YWIgaW4gUlN0dWRpby4gVGhpcyB3aWxsIGxldCB5b3Ugc2Nyb2xsIHRocm91Z2ggdGhlIGRhdGEgYXMgaWYgaXQgd2VyZSBhIHNwcmVhZHNoZWV0LiBJZiB5b3Ugd2FudCB0byBkaXNwbGF5IHNvbWUgb2YgdGhlIGRhdGEgaW4gdGhpcyBkb2N1bWVudCwgY2FsbCB0aGUgYGhlYWQoKWAgZnVuY3Rpb24gdXNpbmcgeW91ciBkYXRhIGZyYW1lIGFzIGEgcGFyYW1ldGVyLiBNYWtlIHN1cmUgdG8gc2Nyb2xsIHRocm91Z2ggYWxsIG9mIHRoZSBjb2x1bW5zIQoKYGBge3J9CiMgSW52ZXN0aWdhdGUgdGhlIGRhdGEKCmhlYWQoYWxsX2RhdGEpCmBgYAoKCgpTaW5jZSB3ZSBoYXZlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBzdGFydGluZyBhbmQgZW5kaW5nIGxvY2F0aW9uIGZvciBlYWNoIHRyaXAsIGxldCdzIHF1aWNrbHkgbWFrZSBhIGhlYXQgbWFwIG9mIHRoZSBzdGFydGluZyBsb2NhdGlvbnMuIE1ha2UgYSBoZWF0IG1hcCB1c2luZyBgZ2dwbG90KClgIGFuZCBgZ2VvbV9iaW4yZCgpYC4gSWYgeW91IG1ha2UgdGhlIGJpbiB3aWR0aCBmb3IgZWFjaCBheGlzIHZlcnkgc21hbGwgKHdlIHVzZWQgYDAuMDAxYCksIHlvdSBzaG91bGQgc2VlIHRoZSBzaGFwZSBvZiBNYW5oYXR0YW4sIEJyb29rbHluLCBhbmQgUXVlZW5zISBDaGVjayBvdXQgdGhlIHJlY3RhbmdsZSBpbiBNYW5oYXR0YW4gd2l0aCBubyBzdGF0aW9ucyAmbWRhc2g7IHRoYXQncyBDZW50cmFsIFBhcmshCgpNYWtlIHN1cmUgdG8gaW5zdGFsbCBhbmQgbG9hZCBgZ2dwbG90MmAgYW5kIGBkcGx5cmAhCgpgYGB7cn0KIyBJbnN0YWxsIGFuZCBsb2FkIGdncGxvdDIgYW5kIGRwbHlyCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShkcGx5cikKIyBDcmVhdGUgYSBoZWF0bWFwCmNpdGliaWtlX2hlYXRtYXAgPC0gYWxsX2RhdGEgJT4lCiAgZ2dwbG90KAogICAgYWVzKAogICAgICB4ID0gc3RhcnQuc3RhdGlvbi5sb25naXR1ZGUsCiAgICAgIHkgPSBzdGFydC5zdGF0aW9uLmxhdGl0dWRlCiAgICApCiAgKSArCiAgZ2VvbV9iaW4yZCgKICAgIGJpbndpZHRoID0gYygwLjAwMSwgMC4wMDEpCiAgKQpjaXRpYmlrZV9oZWF0bWFwCmBgYAoKCldlIGFsc28gaGF2ZSB0aGUgZHVyYXRpb24gb2YgZWFjaCB0cmlwLiBVc2luZyB0aGVzZSBmZWF0dXJlcywgd2UgY2FuIGNhbGN1bGF0ZSB0aGUgYXZlcmFnZSBzcGVlZCBvZiBlYWNoIHRyaXAuCgpGaW5hbGx5LCBzaW5jZSB3ZSBhbHNvIGhhdmUgdGhlIGRhdGUgb2YgYmlydGggZm9yIGVhY2ggcmlkZXIsIHdlIGNhbiBjYWxjdWxhdGUgdGhlaXIgYWdlLgoKTGV0J3Mgd29yayB0b3dhcmRzIGJ1aWxkaW5nIGEgbGluZSBncmFwaCB3aGVyZSBhZ2UgaXMgb24gdGhlIHggYXhpcyBhbmQgYXZlcmFnZSBzcGVlZCBpcyBvbiB0aGUgeSBheGlzLiBUaGlzIGdyYXBoIGNvdWxkIGhlbHAgQ2l0aSBCaWtlIHVuZGVyc3RhbmQgaG93IHRoZWlyIHVzZXJzIGFyZSB1c2luZyB0aGVpciBiaWtlcy4gRm9yIGV4YW1wbGUsIGlmIHlvdW5nZXIgcmlkZXJzIHRlbmQgdG8gYmlrZSBtdWNoIGZhc3RlciB0aGFuIG9sZGVyIHJpZGVycywgQ2l0aSBCaWtlIG1heSB3YW50IHRvIHRoaW5rIGFib3V0IHdheXMgdG8gZW5jb3VyYWdlIHlvdW5nZXIgcmlkZXJzIHRvIGJpa2UgbW9yZSBjYXV0aW91c2x5LgoKQmVmb3JlIHdlIGJlZ2luIHRvIHdvcmsgb24gdGhlIHZpc3VhbGl6YXRpb24sIHdlJ2xsIGhhdmUgdG8gd29yayBhIGJpdCB3aXRoIG91ciBkYXRhc2V0IHRvIGdldCBhbGwgb2YgdGhlIHJlbGV2YW50IGNvbHVtbnMuCgojIyMgTW9kaWZ5aW5nIHRoZSBEYXRhIEZyYW1lOiBTdWJzZXQgYW5kIEFnZQoKU2luY2UgdGhpcyBkYXRhc2V0IGlzIHNvIGJpZywgd2UgcmVjb21tZW5kIHVzaW5nIHRoZSBgZmlsdGVyKClgIGZ1bmN0aW9uIHRvIGdyYWIgYSBzdWJzZXQgb2YgdGhlIGRhdGEuIEZvciBleGFtcGxlLCB5b3UgY291bGQgZ3JhYiBvbmx5IHRoZSByb3dzIHdoZXJlIHRoZSBkdXJhdGlvbiB3YXMgdW5kZXIgOTAwIHNlY29uZHMgKDE1IG1pbnV0ZXMpLiBXZSBzdG9yZWQgdGhlc2Ugcm93cyBpbiBhIG5ldyBkYXRhIGZyYW1lIG5hbWVkIGBzaG9ydF90cmlwc2AuIE5vdGUgdGhhdCB3ZSBkaWQgdGhpcyBvbmx5IHRvIHNwZWVkIHVwIHRoZSBydW50aW1lIG9mIHVwY29taW5nIG9wZXJhdGlvbnMuIFRoaXMgaXMgY29tcGxldGVseSBvcHRpb25hbCAmbWRhc2g7IGlmIHlvdSdyZSBoYXBweSB0byB3YWl0IGEgYml0IGZvciBlYWNoIG9wZXJhdGlvbiwgZmVlbCBmcmVlIHRvIHVzZSB0aGUgY29tcGxldGUgZGF0YSBzZXQuCgpgYGB7cn0KIyBDcmVhdGUgYSBzdWJzZXQgb2YgdGhlIGRhdGEKc2hvcnRfdHJpcHMgPC0gYWxsX2RhdGEgJT4lCiAgZmlsdGVyKHRyaXBkdXJhdGlvbiA8IDkwMCkKaGVhZChzaG9ydF90cmlwcykKYGBgCgpOZXh0LCBsZXQncyBhZGQgYSBjb2x1bW4gY2FsbGVkIGBhZ2VgIHRvIHRoZSBkYXRhIGZyYW1lLiBgYWdlYCBzaG91bGQgYmUgMjAyMCBtaW51cyBgYmlydGgueWVhcmAgKHRoaXMgZGF0YSB3YXMgY29sbGVjdGVkIGluIDIwMjApLiBVc2UgdGhlIGBtdXRhdGUoKWAgZnVuY3Rpb24gdG8gZG8gdGhpcy4gQWZ0ZXIgY2FsbGluZyBtdXRhdGUsIG1ha2Ugc3VyZSB0byBzYXZlIHRoZSByZXN1bHQgaW4gYSB2YXJpYWJsZS4gV2Ugc2hvdWxkIHNhdmUgdGhlIHJlc3VsdCBiYWNrIHRvIGBzaG9ydF90cmlwc2AuCgpgYGB7cn0KIyBBZGQgdGhlIGFnZSBjb2x1bW4Kc2hvcnRfdHJpcHMgPC0gc2hvcnRfdHJpcHMgJT4lCiAgbXV0YXRlKAogICAgYWdlID0gMjAyMCAtIGJpcnRoLnllYXIKICApCmhlYWQoc2hvcnRfdHJpcHMpCmBgYAoKIyMjIE1vZGlmeWluZyB0aGUgRGF0YSBGcmFtZTogRGlzdGFuY2UKCkluIG9yZGVyIHRvIGNhbGN1bGF0ZSB0aGUgc3BlZWQgb2YgZWFjaCBiaWtlciwgd2UgbmVlZCB0byBmaW5kIHRoZSB0b3RhbCBkaXN0YW5jZSB0aGV5IHRyYXZlbGVkLiBMdWNraWx5LCB3ZSBoYXZlIGluZm9ybWF0aW9uIGFib3V0IHRoZSBzdGFydGluZyBhbmQgZW5kaW5nIGxhdGl0dWRlcyBhbmQgbG9uZ2l0dWRlcy4gTGV0J3MgdXNlIHRob3NlIGZvdXIgY29sdW1ucyB0byBjcmVhdGUgYSBuZXcgY29sdW1uIG5hbWVkIGBkaXN0YW5jZWAuIAoKVGhlcmUgYXJlIG1hbnkgZGlmZmVyZW50IHdheXMgdG8gY2FsY3VsYXRlIGRpc3RhbmNlLiBXZSdsbCB3YWxrIHlvdSB0aHJvdWdoIHRoZSBzdHJhdGVneSB3ZSB1c2VkLiBIb3dldmVyLCBiZWZvcmUgZm9sbG93aW5nIGFsb25nIHdpdGggdXMsIGNoYWxsZW5nZSB5b3Vyc2VsZiB0byBzb2x2ZSB0aGlzIHByb2JsZW0gb24geW91ciBvd24gJm1kYXNoOyBvbmUgb2YgdGhlIGdvYWxzIG9mIHRoZXNlIG9mZiBwbGF0Zm9ybSBwcm9qZWN0cyBpcyB0byBnZXQgY29tZm9ydGFibGUgcHJvYmxlbSBzb2x2aW5nIG9uIHlvdXIgb3duLiBUcnkgdG8gdXNlIEdvb2dsZSB0byBmaW5kIHRoZSBwYWNrYWdlcyB5b3UgbWlnaHQgbmVlZCB0byBjYWxjdWxhdGUgdGhlIGRpc3RhbmNlIGJldHdlZW4gbGF0aXR1ZGUgYW5kIGxvbmdpdHVkZSBjb29yZGluYXRlcy4gVXNlIHRoZSBjb2RlIGJsb2NrIGJlbG93IHRvIHRyeSBzb2x2aW5nIHRoaXMgcHJvYmxlbSBvbiB5b3VyIG93bi4gV2UnbGwgd2FsayB5b3UgdGhyb3VnaCBvdXIgc29sdXRpb24gaW4gdGhlIGZvbGxvd2luZyBzZWN0aW9uLgoKQXMgeW91IHdyaXRlIHlvdXIgY29kZSB0aGF0IGVkaXRzIHRoZSBkYXRhIGZyYW1lLCBjb25zaWRlciBwcmludGluZyB0aGUgaGVhZCBvZiB0aGUgZGF0YSBmcmFtZSB0byB2YWxpZGF0ZSB0aGUgd29yayB5b3UgYXJlIGRvaW5nIQoKYGBge3J9CiMgVHJ5IGNyZWF0aW5nIGEgZGlzdGFuY2UgY29sdW1uIGluIHlvdXIgZGF0YSBmcmFtZSBoZXJlOgoKYGBgCgpUaGVyZSBhcmUgbWFueSBkaWZmZXJlbnQgc3RyYXRlZ2llcyB0byBjYWxjdWxhdGUgdGhlIGRpc3RhbmNlIGJldHdlZW4gdHdvIHBvaW50cy4gVGhlIHNpbXBsZXN0IHdheSB0byBkbyB0aGlzIHdvdWxkIGJlIHRvIGZpbmQgdGhlIGxlbmd0aCBvZiB0aGUgc3RyYWlnaHQgbGluZSBiZXR3ZWVuIHRoZSB0d28gcG9pbnRzLiBUaGlzIGlzIGEgbWFzc2l2ZSBhc3N1bXB0aW9uIHRvIG1ha2UgJm1kYXNoOyBpdCB3b3VsZCBiZSByZW1hcmthYmxlIGlmIGFueSBvZiB0aGVzZSBiaWtlIHRyaXBzIHRyYXZlbGVkIGluIGEgc3RyYWlnaHQgbGluZSBiZXR3ZWVuIHRoZSB0d28gcG9pbnRzIHdpdGhvdXQgbWFraW5nIGFueSB0dXJucyBvciBjdXJ2ZXMuCgpUaGF0IGJlaW5nIHNhaWQsIGZpbmRpbmcgdGhlIHN0cmFpZ2h0IGxpbmUgZGlzdGFuY2UgaXMgYSBnb29kIHN0YXJ0aW5nIHBvaW50LiBUaGUgYGRpc3RIYXZlcnNpbmUoKWAgZnVuY3Rpb24gZm91bmQgaW4gdGhlIGBnZW9zcGhlcmVgIGxpYnJhcnkgY2FuIGNhbGN1bGF0ZSB0aGlzIGRpc3RhbmNlLgoKRmlyc3QsIGluc3RhbGwgYW5kIGxvYWQgdGhlIGBnZW9zcGhlcmVgIGxpYnJhcnkuCgpOZXh0LCB1c2UgYGRwbHlyYCdzIGBzZWxlY3QoKWAgZnVuY3Rpb24gdG8gY3JlYXRlIHR3byBuZXcgZGF0YSBmcmFtZXMgdGhhdCBjb250YWluIG9ubHkgdGhlIGxhdGl0dWRlcyBhbmQgbG9uZ2l0dWRlcyBvZiB0aGUgc3RhcnRpbmcgYW5kIGVuZGluZyBwb2ludHMuIFdlIGNhbGxlZCB0aGVzZSBkYXRhIGZyYW1lcyBgc3RhcnRpbmdfc3RhdGlvbnNgIGFuZCBgZW5kaW5nX3N0YXRpb25zYC4KCkZpbmFsbHksIHVzZSBgZHBseXJgJ3MgYG11dGF0ZSgpYCBmdW5jdGlvbiB0byBhZGQgYSBjb2x1bW4gbmFtZWQgYGRpc3RhbmNlYCB0byB5b3VyIGRhdGEuIGBkaXN0YW5jZWAgc2hvdWxkIGJlIGNhbGN1bGF0ZWQgYnkgY2FsbGluZyBgZGlzdEhhdmVyc2luZSgpYCB1c2luZyBgc3RhcnRpbmdfc3RhdGlvbnNgIGFuZCBgZW5kaW5nX3N0YXRpb25zYCBhcyBwYXJhbWV0ZXJzLgoKSWYgeW91IGdldCBzdHVjaywgdXNlIGA/ZGlzdEhhdmVyc2luZWAgdG8gY2hlY2sgdGhlIGRvY3VtZW50YXRpb24gdG8gc2VlIG1vcmUgZXhhbXBsZXMhIFlvdSBjYW4gYWxzbyB1c2UgdGhlIGRvY3VtZW50YXRpb24gdG8gZmluZCB0aGUgdW5pdHMgb2YgdGhlIHJlc3VsdCBvZiBgZGlzdEhhdmVyc2luZSgpYCEKCmBgYHtyfQojIFVzZSB0aGUgZ2Vvc3BoZXJlIGxpYnJhcnkgdG8gY3JlYXRlIGEgZGlzdGFuY2UgY29sdW1uCiMgaW5zdGFsbC5wYWNrYWdlcygiZ2Vvc3BoZXJlIikKCmxpYnJhcnkoZ2Vvc3BoZXJlKQoKc3RhcnRpbmdfc3RhdGlvbnMgPC0gc2hvcnRfdHJpcHMgJT4lCiAgc2VsZWN0KHN0YXJ0LnN0YXRpb24ubGF0aXR1ZGUsIHN0YXJ0LnN0YXRpb24ubG9uZ2l0dWRlKQplbmRpbmdfc3RhdGlvbnMgPC0gc2hvcnRfdHJpcHMgJT4lCiAgc2VsZWN0KGVuZC5zdGF0aW9uLmxhdGl0dWRlLCBlbmQuc3RhdGlvbi5sb25naXR1ZGUpCgpzaG9ydF90cmlwcyA8LSBzaG9ydF90cmlwcyAlPiUKICBtdXRhdGUoCiAgICBkaXN0YW5jZSA9IGRpc3RIYXZlcnNpbmUoc3RhcnRpbmdfc3RhdGlvbnMsIGVuZGluZ19zdGF0aW9ucykKICApCgpoZWFkKHNob3J0X3RyaXBzKQpgYGAKCiMjIyBNb2RpZnlpbmcgdGhlIERhdGEgRnJhbWU6IFNwZWVkCgpOb3cgdGhhdCB3ZSd2ZSBtYWRlIGEgY29sdW1uIGNvbnRhaW5pbmcgdGhlIGRpc3RhbmNlIG9mIGVhY2ggdHJpcCwgbGV0J3MgbWFrZSBhbm90aGVyIGNvbHVtbiBjb250YWluaW5nIHRoZSBhdmVyYWdlIHNwZWVkIG9mIGVhY2ggdHJpcC4gVGhpcyBjb2x1bW4gc2hvdWxkIGJlIGVhc2llciB0byBjcmVhdGUgdGhhbiB0aGUgcHJldmlvdXMgJm1kYXNoOyBzcGVlZCBjYW4gYmUgY2FsY3VsYXRlZCBieSBkaXZpZGluZyB0aGUgYGRpc3RhbmNlYCBjb2x1bW4gYnkgdGhlIGB0cmlwZHVyYXRpb25gIGNvbHVtbi4gVGhpcyB3aWxsIGdpdmUgdXMgdGhlIGF2ZXJhZ2Ugc3BlZWQgaW4gbWV0ZXJzIHBlciBzZWNvbmQuIFVzZSB0aGUgYG11dGF0ZSgpYCBmdW5jdGlvbiB0byBtYWtlIHRoZSBgc3BlZWRgIGNvbHVtbiEKCmBgYHtyfQojIENyZWF0ZSB0aGUgc3BlZWQgY29sdW1uCnNob3J0X3RyaXBzIDwtIHNob3J0X3RyaXBzICU+JQogIG11dGF0ZSgKICAgIHNwZWVkID0gZGlzdGFuY2UgLyB0cmlwZHVyYXRpb24KICApCmhlYWQoc2hvcnRfdHJpcHMpCmBgYAoKIyMjIE1vZGlmeWluZyB0aGUgRGF0YSBGcmFtZTogQXZlcmFnZSBTcGVlZCBieSBBZ2UKCldlJ3JlIGFsbW9zdCB0aGVyZSEgTm93IHRoYXQgd2UgaGF2ZSB0aGUgc3BlZWQgb2YgZXZlcnkgYmlrZSB0cmlwLCB3ZSB3YW50IHRvIGdyb3VwIHRob3NlIHRyaXBzIGJ5IGBhZ2VgIGFuZCBmaW5kIHRoZSBhdmVyYWdlIHNwZWVkIG9mIGVhY2ggYWdlLiAKCkRvIHRoaXMgYnkgcGlwaW5nIHlvdXIgZGF0YSBmcmFtZSBpbnRvIHRoZSBgZ3JvdXBfYnkoKWAgZnVuY3Rpb24gdXNpbmcgYGFnZWAgYXMgYSBwYXJhbWV0ZXIuIAoKVGhlbiBwaXBlIHRoZSByZXN1bHQgb2YgdGhhdCBmdW5jdGlvbiBpbnRvIHRoZSBgc3VtbWFyaXplKClgIGZ1bmN0aW9uLiBgc3VtbWFyaXplKClgIHdvcmtzIHNpbWlsYXJseSB0byBgbXV0YXRlKClgICZtZGFzaDsgcGFzcyBgbWVhbl9zcGVlZCA9IG1lYW4oc3BlZWQpYCB0byB0aGUgYHN1bW1hcml6ZSgpYCBmdW5jdGlvbiB0byBjcmVhdGUgYSBuZXcgY29sdW1uIG5hbWVkIGBtZWFuX3NwZWVkYC4gU2F2ZSB0aGlzIG5ldyBkYXRhIGZyYW1lIGluIGEgdmFyaWFibGUgY2FsbGVkIGBhdmVyYWdlX3NwZWVkX2J5X2FnZWAuCgoKYGBge3J9CiMgVXNlIGdyb3VwX2J5KCkgYW5kIHN1bW1hcml6ZSgpIHRvIGdldCB0aGUgbWVhbiBzcGVlZCBvZiBlYWNoIGFnZQphdmVyYWdlX3NwZWVkX2J5X2FnZSA8LSBzaG9ydF90cmlwcyAlPiUKICBncm91cF9ieShhZ2UpICU+JQogICAgc3VtbWFyaXNlKG1lYW5fc3BlZWQgPSBtZWFuKHNwZWVkKSkKaGVhZChhdmVyYWdlX3NwZWVkX2J5X2FnZSkKYGBgCgojIyMgVmlzdWFsaXphdGlvbiEKCldlIG1hZGUgaXQhIFdlIG5vdyBoYXZlIHRoZSBhdmVyYWdlIHNwZWVkIG9mIGV2ZXJ5IGFnZSBpbiBvdXIgZGF0YXNldC4gTGV0J3MgdXNlIGBnZ3Bsb3QyYCB0byBtYWtlIGEgbGluZSBncmFwaCB0byBzZWUgaWYgeW91bmdlciBwZW9wbGUgcmVhbGx5IGRvIGJpa2UgZmFzdGVyLiBNYWtlIHN1cmUgdG8gaW5zdGFsbCBhbmQgbG9hZCBgZ2dwbG90MmAgaWYgeW91IGhhdmVuJ3QgZG9uZSBzbyBhbHJlYWR5LiBQYXNzIHlvdXIgZGF0YSBmcmFtZSB0byBgZ2dwbG90KClgIGFuZCBhZGQgYSBgZ2VvbV9saW5lKClgLiBgZ2VvbV9saW5lKClgIHNob3VsZCBjb250YWluIGFuIGFlc3RoZXRpYyB3aGVyZSBgeCA9IGFnZWAgYW5kIGB5ID0gbWVhbl9zcGVlZGAuCgpgYGB7cn0KIyBJbnN0YWxsIGFuZCBsb2FkIGdncGxvdDIgdG8gY3JlYXRlIGEgbGluZSBncmFwaCBvZiBhZ2UgYW5kIG1lYW4gc3BlZWQKYXZlcmFnZV9zcGVlZF9ieV9hZ2UgJT4lIAogIGdncGxvdCgKICAgIGFlcygKICAgICAgeCA9IGFnZSwKICAgICAgeSA9IG1lYW5fc3BlZWQKICAgICkKICApICsKICBnZW9tX2xpbmUoKQpgYGAKCk5pY2Ugd29yayEgT3VyIGludHVpdGlvbiBzZWVtcyB0byBiZSByaWdodCAmbWRhc2g7IHRoZXJlJ3MgYSBzdGVhZHkgZHJvcCBpbiBzcGVlZCB1bnRpbCB3ZSBoaXQgc29tZSBvdXRsaWVycy4gSXQgd291bGQgYmUgcHJldHR5IHN1cnByaXNpbmcgdG8gc2VlIHNvbWVvbmUgb3ZlciB0aGUgYWdlIG9mIDEwMCB1c2luZyBhIGJpa2Ugc2hhcmUgcHJvZ3JhbSEgTGV0J3MgZmlsdGVyIHRoZSBkYXRhIHRvIG9ubHkgc2hvdyBhZ2VzIGxlc3MgdGhhbiA4MCBhbmQgcmVkcmF3IG91ciB2aXN1YWxpemF0aW9uLgoKYGBge3J9CiMgRmlsdGVyIHRoZSBkYXRhIGZyYW1lIHRvIG9ubHkgY29udGFpbiByb3dzIHdoZXJlIHRoZSBhZ2UgaXMgbGVzcyB0aGFuIDgwCmF2ZXJhZ2Vfc3BlZWRfYnlfYWdlIDwtIGF2ZXJhZ2Vfc3BlZWRfYnlfYWdlICU+JQogIGZpbHRlcihhZ2UgPCA4MCkKYXZlcmFnZV9zcGVlZF9ieV9hZ2UgJT4lCiAgZ2dwbG90KAogICAgYWVzKAogICAgICB4ID0gYWdlLAogICAgICB5ID0gbWVhbl9zcGVlZAogICAgKQogICkgKyAKICBnZW9tX2xpbmUoKQpgYGAKClRoYXQgbG9va3MgYSBiaXQgYmV0dGVyISBMZXQncyBkbyBzb21lIHdvcmsgdG8gbWFrZSBvdXIgZ3JhcGggbG9vayBhIGJpdCBtb3JlIHByb2Zlc3Npb25hbC4gQWRkIGEgdGl0bGUgYW5kIGF4aXMgbGFiZWxzLiBXZSBhbHNvIGNlbnRlcmVkIG91ciB0aXRsZSEKCmBgYHtyfQojIEFkZCBhIHRpdGxlIGFuZCBsYWJlbCB0aGUgYXhlcwphdmVyYWdlX3NwZWVkX2J5X2FnZSAlPiUKICBnZ3Bsb3QoCiAgICBhZXMoCiAgICAgIHggPSBhZ2UsCiAgICAgIHkgPSBtZWFuX3NwZWVkCiAgICApCiAgKSArIAogIGdlb21fbGluZSgpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQXZlcmFnZSBTcGVlZCBvZiBDaXRpIEJpa2UgdXNlcnMgYnkgYWdlIChKYW51YXJ5IDIwMjApIiwKICAgIHggPSAiQWdlIiwKICAgIHkgPSAiTWVhbiBTcGVlZCAobS9zKSIKICApICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKYGBgCgojIyMgRmlsdGVyaW5nIEJ5IEdlbmRlcgoKR3JlYXQgd29yayEgVGhpcyB2aXN1YWxpemF0aW9uIGdpdmVzIHVzIHNvbWUgZ3JlYXQgaW5zaWdodHMgb24gaG93IENpdGkgQmlrZSB1c2VycyBhcmUgdXNpbmcgdGhlaXIgYmlrZXMuIExldCdzIGRpdmUgZXZlbiBkZWVwZXIhIFdlIGNhbiBncm91cCBvdXIgZGF0YSBieSBtb3JlIHRoYW4gb25lIGZlYXR1cmUuIAoKRmluZCB5b3VyIGxpbmUgb2YgY29kZSB0aGF0IGdyb3VwZWQgb3VyIGRhdGEgYnkgYGFnZWAuIENvcHkgaXQsIGJ1dCBhZGQgYGdlbmRlcmAgYXMgYSBwYXJhbWV0ZXIgdG8gdGhlIGBncm91cF9ieSgpYCBmdW5jdGlvbi4gU2F2ZSB0aGUgcmVzdWx0IGluIGEgZGF0YSBmcmFtZSBuYW1lZCBgYXZlcmFnZV9zcGVlZF9ieV9hZ2VfYW5kX2dlbmRlcmAuIEluc3BlY3QgdGhpcyBkYXRhIGZyYW1lIHRvIHNlZSB3aGF0IGl0IGNvbnRhaW5zLgoKCmBgYHtyfQojIFVzZSBncm91cF9ieSgpIGFnYWluIHRvIGdyb3VwIGJ5IGJvdGggYWdlIGFuZCBnZW5kZXIKYXZlcmFnZV9zcGVlZF9ieV9hZ2VfYW5kX2dlbmRlciA8LSBzaG9ydF90cmlwcyAlPiUKICBncm91cF9ieShhZ2UsIGdlbmRlcikgJT4lCiAgICBzdW1tYXJpc2UobWVhbl9zcGVlZCA9IG1lYW4oc3BlZWQpKQphdmVyYWdlX3NwZWVkX2J5X2FnZV9hbmRfZ2VuZGVyCmBgYAoKTGV0J3Mgbm93IHZpc3VhbGl6ZSB0aGUgZGlmZmVyZW5jZSBpbiBhdmVyYWdlIHNwZWVkIGJ5IGFnZSBfYW5kXyBnZW5kZXIuIE5vdGUgdGhhdCBpZiB5b3UgbG9vayBpbiB0aGUgZG9jdW1lbnRhdGlvbiBmb3IgdGhlIGRhdGEsIGEgYDBgIHJlcHJlc2VudHMgYSB1c2VyIHRoYXQgZGlkbid0IHNwZWNpZnkgdGhlaXIgZ2VuZGVyIGFzIG1hbGUgb3IgZmVtYWxlLCBhIGAxYCByZXByZXNlbnRzIGEgdXNlciBpZGVudGlmeWluZyBhcyBtYWxlLCBhbmQgYSBgMmAgcmVwcmVzZW50cyBhIHVzZXIgaWRlbnRpZnlpbmcgYXMgZmVtYWxlLgoKVGhlIHByZXZpb3VzIGNhbGwgdG8gYGdncGxvdCgpYCBhbmQgYGdlb21fbGluZSgpYCBzaG91bGQgYmUgY2xvc2UgdG8gd2hhdCB3ZSB3YW50LiBBZGQgdGhlIHBhcmFtZXRlciBgY29sb3IgPSBnZW5kZXJgIHRvIHRoZSBhZXN0aGV0aWMgaW4gYGdlb21fbGluZSgpYC4gTWFrZSBzdXJlIHlvdSB1c2UgdGhlIG5ldyBkYXRhIGNvbnRhaW5pbmcgdGhlIGdlbmRlciBpbmZvcm1hdGlvbiEgWW91IG9uY2UgYWdhaW4gbWF5IHdhbnQgdG8gZmlsdGVyIG91dCB0aGUgYWdlcyBncmVhdGVyIHRoYW4gODAuCgpOb3RlIHRoYXQgdGhpcyBncmFwaCB3b24ndCBxdWl0ZSBiZSB3aGF0IHdlIHdhbnQgeWV0LCBidXQgd2UncmUgZ2V0dGluZyBjbG9zZSEKCmBgYHtyfQojIE1ha2UgYSBsaW5lIGdyYXBoIG9mIHlvdXIgbmV3IGZpbHRlcmVkIGRhdGEgZnJhbWUKYXZlcmFnZV9zcGVlZF9ieV9hZ2VfYW5kX2dlbmRlciA8LSBhdmVyYWdlX3NwZWVkX2J5X2FnZV9hbmRfZ2VuZGVyICU+JQogIGZpbHRlcihhZ2UgPCA4MCkKCmF2ZXJhZ2Vfc3BlZWRfYnlfYWdlX2FuZF9nZW5kZXIgJT4lCiAgZ2dwbG90KAogICAgYWVzKAogICAgICB4ID0gYWdlLAogICAgICB5ID0gbWVhbl9zcGVlZCwKICAgICAgY29sb3IgPSBnZW5kZXIKICAgICkKICApICsgCiAgZ2VvbV9saW5lKCkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJBdmVyYWdlIFNwZWVkIG9mIENpdGkgQmlrZSB1c2VycyBieSBhZ2UgKEphbnVhcnkgMjAyMCkiLAogICAgeCA9ICJBZ2UiLAogICAgeSA9ICJNZWFuIFNwZWVkIChtL3MpIgogICkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQpgYGAKCkl0J3MgYSBiaXQgaGFyZCB0byB0ZWxsIHdoYXQgaXMgaGFwcGVuaW5nIGluIHRoYXQgZ3JhcGggJm1kYXNoOyBidXQgb25lIG9kZGl0eSB0aGF0IHN0aWNrcyBvdXQgaXMgdGhlIHNjYWxlIHVzZWQgZm9yIGdlbmRlci4gV2Uga25vdyB0aGF0IG91ciBnZW5kZXIgZGF0YSBpcyByZXByZXNlbnRlZCBhcyB0aHJlZSBkaXN0aW5jdCB2YWx1ZXMgJm1kYXNoOyBgMGAsIGAxYCwgYW5kIGAyYC4gSG93ZXZlciwgYGdncGxvdCgpYCBpcyB1c2luZyBnZW5kZXIgYXMgYSBjb250aW51b3VzIHZhcmlhYmxlLiAKCldlIGNhbiB0dXJuIHRoaXMgY29sdW1uIGludG8gYSBmYWN0b3IgYnkgdXNpbmcgdGhlIGBhcy5mYWN0b3IoKWAgYW5kIGBtdXRhdGUoKWAgZnVuY3Rpb25zLiBQaXBlIHlvdXIgZGF0YSBmcmFtZSBpbnRvIHRoZSBgbXV0YXRlKClgIGZ1bmN0aW9uIGFuZCB1c2UgIGBnZW5kZXIgPSBhcy5mYWN0b3IoZ2VuZGVyKWAgYXMgdGhlIHBhcmFtZXRlci4KCk5vdGUgdGhhdCB3aGVuIHlvdSBtYWtlIHRoaXMgY29sdW1uIGEgZmFjdG9yLCB5b3Ugd2lsbCBzZWUgYSBudW1iZXIgb2Ygd2FybmluZ3MuIFRoaXMgd2FybmluZyBpcyB0ZWxsaW5nIHlvdSB0aGF0IHRoZSB0eXBlIG9mIHRoZSB2YWx1ZXMgaW4gdGhlIGdlbmRlciBjb2x1bW4gaGF2ZSBiZWVuIGNoYW5nZWQgZnJvbSBpbnRlZ2VycyB0byBjaGFyYWN0ZXJzLgoKVGhlbiByZWRyYXcgeW91ciBncmFwaC4KCmBgYHtyIHdhcm5pbmc9RkFMU0V9CiMgVXNlIG11dGF0ZSgpIGFuZCBhcy5mYWN0b3IoKSB0byBjaGFuZ2UgdGhlIGdlbmRlciBjb2x1bW4gaW50byBhIGZhY3Rvci4KYXZlcmFnZV9zcGVlZF9ieV9hZ2VfYW5kX2dlbmRlciA8LSBhdmVyYWdlX3NwZWVkX2J5X2FnZV9hbmRfZ2VuZGVyICU+JQogIG11dGF0ZSgKICAgIGdlbmRlciA9IGFzLmZhY3RvcihnZW5kZXIpCiAgKQphdmVyYWdlX3NwZWVkX2J5X2FnZV9hbmRfZ2VuZGVyICU+JQogIGdncGxvdCgKICAgIGFlcygKICAgICAgeCA9IGFnZSwKICAgICAgeSA9IG1lYW5fc3BlZWQsCiAgICAgIGNvbG9yID0gZ2VuZGVyCiAgICApCiAgKSArIAogIGdlb21fbGluZSgpICsKICBsYWJzKAogICAgdGl0bGUgPSAiQXZlcmFnZSBTcGVlZCBvZiBDaXRpIEJpa2UgdXNlcnMgYnkgYWdlIChKYW51YXJ5IDIwMjApIiwKICAgIHggPSAiQWdlIiwKICAgIHkgPSAiTWVhbiBTcGVlZCAobS9zKSIKICApICsKICB0aGVtZShwbG90LnRpdGxlID0gZWxlbWVudF90ZXh0KGhqdXN0ID0gMC41KSkKYGBgCgpOaWNlIHdvcmshIFdlIGNhbiBub3cgc2VlIHRoZSBhdmVyYWdlIHNwZWVkcyBieSBhZ2UgYnJva2VuIGludG8gdGhlIDMgZ2VuZGVycyBDaXRpIEJpa2UgYWNjb3VudHMgZm9yLiBZb3UgY2FuIHNlZSB0aGF0IG1hbGUtaWRlbnRpZnlpbmcgdXNlcnMgdHlwaWNhbGx5IGJpa2UgZmFzdGVyIHRoYW4gZmVtYWxlLWlkZW50aWZ5aW5nIHVzZXJzLiBVc2VycyB3aXRoIGFuIHVua25vd24gZ2VuZGVyIGRvbid0IGZvbGxvdyBhIHNwZWNpZmljIHBhdHRlcm4gJm1kYXNoOyBpdCBpcyBsaWtlbHkgdGhhdCB0aGVyZSBpc24ndCBlbm91Z2ggZGF0YSB0byBwcm9wZXJseSB2aXN1YWxpemUgdGhvc2UgdXNlcnMuCgpGb3Igb3VyIGZpbmFsIHZlcnNpb24gb2YgdGhpcyBncmFwaCwgd2UgZmlsdGVyZWQgb3V0IHRoZSB1c2VycyB3aXRoIGEgZ2VuZGVyIG9mIGAwYCwgYW5kIHdlIGxhYmVsZWQgdGhlIGxpbmVzIGFzICJNYWxlIElkZW50aWZ5aW5nIiBhbmQgIkZlbWFsZSBJZGVudGlmeWluZyIgdXNpbmcgdGhlIGBzY2FsZV9jb2xvcl9kaXNjcmV0ZSgpYCBmdW5jdGlvbi4gVGFrZSBhIGxvb2sgYXQgdGhlIGRvY3VtZW50YXRpb24gZm9yIHRoaXMgZnVuY3Rpb24gdXNpbmcgYD9zY2FsZV9jb2xvcl9kaXNjcmV0ZWAgdG8gY2hhbmdlIHRoZSBsYWJlbCBvZiBlYWNoIGxpbmUuCgpgYGB7cn0KIyBGaWx0ZXIgdGhlIGRhdGEgZnJhbWUgdG8gb25seSBpbmNsdWRlIGdlbmRlcnMgMSBhbmQgMi4gU2V0IGFwcHJvcHJpYXRlIGxhYmVscyBmb3IgdGhlIGxlZ2VuZAphdmVyYWdlX3NwZWVkX2J5X2FnZV9hbmRfZ2VuZGVyIDwtIGF2ZXJhZ2Vfc3BlZWRfYnlfYWdlX2FuZF9nZW5kZXIgJT4lCiAgZmlsdGVyKAogICAgZ2VuZGVyID09IDEgfCBnZW5kZXIgPT0gMgogICkKCmF2ZXJhZ2Vfc3BlZWRfYnlfYWdlX2FuZF9nZW5kZXIgJT4lCiAgZ2dwbG90KAogICAgYWVzKAogICAgICB4ID0gYWdlLAogICAgICB5ID0gbWVhbl9zcGVlZCwKICAgICAgY29sb3IgPSBnZW5kZXIKICAgICkKICApICsgCiAgZ2VvbV9saW5lKCkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJBdmVyYWdlIFNwZWVkIG9mIENpdGkgQmlrZSB1c2VycyBieSBhZ2UgKEphbnVhcnkgMjAyMCkiLAogICAgeCA9ICJBZ2UiLAogICAgeSA9ICJNZWFuIFNwZWVkIChtL3MpIgogICkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArIAogIHNjYWxlX2NvbG9yX2Rpc2NyZXRlKAogICAgbmFtZSA9ICJHZW5kZXIiLAogICAgbGFiZWxzID0gYygKICAgICAgIjEiID0gIk1hbGUiLAogICAgICAiMiIgPSAiRmVtYWxlIgogICAgKQogICkKYGBgCgojIyMgTWFraW5nIGEgU3RhY2tlZCBCYXIgUGxvdCBPZiBBZ2VzCgpMZXQncyBtYWtlIG9uZSBmaW5hbCBncmFwaC4gRm9yIHRoaXMgZ3JhcGgsIHdlJ3JlIGludGVyZXN0ZWQgaW4gc2VlaW5nIHRoZSBkaXN0cmlidXRpb24gb2YgQ2l0aSBCaWtlIHVzZXJzJyBhZ2UgYW5kIGdlbmRlci4gTGV0J3MgdXNlIGEgc3RhY2tlZCBiYXIgcGxvdCB0byBkbyB0aGlzLiBXZSdsbCB3YW50IHRvIGNyZWF0ZSBhIGJhciBwbG90IHdoZXJlIGFnZSBpcyBvbiB0aGUgeCBheGlzLCBjb3VudCBpcyBvbiB0aGUgeSBheGlzLCBhbmQgZWFjaCBiYXIgaXMgc3BsaXQgaW50byBnZW5kZXJzLgoKTGV0J3Mgc3RhcnQgYnkgdXNpbmcgb3VyIGBzaG9ydF90cmlwc2AgZGF0YXNldC4gV2UnbGwgd2FudCB0byBjYWxsIGBncm91cF9ieSgpYCB1c2luZyB0aGlzIGRhdGFzZXQgYW5kIHBpcGUgdGhlIHJlc3VsdCB0byBgdGFsbHkoKWAuIFRoaXMgd2lsbCBsZXQgdXMgZ2V0IGEgY291bnQgb2YgdGhlIG51bWJlciBvZiBiaWtlcnMgZm9yIGVhY2ggYWdlIGFuZCBnZW5kZXIuIFdlIHNhdmVkIHRoaXMgbmV3IGRhdGEgZnJhbWUgaW4gYSB2YXJpYWJsZSBjYWxsZWQgYGFnZV9jb3VudHNgLgoKYGBge3J9CiMgQ3JlYXRlIHRoZSBhZ2VfY291bnRzIGRhdGEgZnJhbWUKYWdlX2NvdW50cyA8LSBzaG9ydF90cmlwcyAlPiUKICBncm91cF9ieSgKICAgIGFnZSwgZ2VuZGVyCiAgKSAlPiUKICAgIHRhbGx5KCkKaGVhZChhZ2VfY291bnRzKQpgYGAKCklmIHlvdSBsb29rIGF0IHRoZSBoZWFkIG9mIHRoaXMgbmV3IGRhdGEgZnJhbWUsIHlvdSdsbCBzZWUgdGhlIGNvdW50cyBhcmUgc3RvcmVkIGluIGEgY29sdW1uIG5hbWVkIGBuYC4gTGV0J3Mgbm93IHVzZSBgZ2dwbG90KClgIGFuZCBgZ2VvbV9jb2woKWAgdG8gY3JlYXRlIGEgc3RhY2tlZCBiYXIgcGxvdC4gYGdncGxvdCgpYCBzaG91bGQgaGF2ZSBhbiBgYWVzKClgIHdoZXJlIGB4ID0gYWdlYCwgYHkgPSBuYCBhbmQgYGZpbGwgPSBnZW5kZXJgLgoKYGBge3J9CiMgQ3JlYXRlIHRoZSBzdGFja2VkIGJhciBwbG90CmFnZV9jb3VudHMgJT4lCiAgZ2dwbG90KAogICAgYWVzKAogICAgICB4ID0gYWdlLAogICAgICB5ID0gbiwKICAgICAgZmlsbCA9IGdlbmRlcgogICAgKQogICkgKwogIGdlb21fY29sKCkKYGBgCgpHcmVhdCEgVGhlcmUgYXJlIHNvbWUgdHdlYWtzIHRoYXQgd2UgbWlnaHQgd2FudCB0byBtYWtlIHRvIHRoaXMgZ3JhcGguIEZpcnN0LCBnZW5kZXIgcmlnaHQgbm93IGlzIHJlcHJlc2VudGVkIGFzIGFuIGludGVnZXIuIEl0IHdpbGwgbWFrZSBtb3JlIHNlbnNlIGlmIHRoYXQgY29sdW1uIGlzIHJlcHJlc2VudGVkIGFzIGEgZmFjdG9yLiBUbyBkbyB0aGlzLCB3ZSBjYW4gcGFzcyBgYXMuZmFjdG9yKGdlbmRlcilgIGFzIHRoZSB2YWx1ZSBmb3IgdGhlIHggYXhpcy4KCk5leHQsIGl0IGxvb2tzIGxpa2Ugd2UgaGF2ZSBzb21lIHVudXN1YWwgZGF0YSBhcm91bmQgdGhlIGFnZSBvZiA1MC4gSXQgbG9va3MgbGlrZSB0aGVyZSBhcmUgYSB0b24gb2YgYmlrZXJzIHdpdGggYW4gdW5rbm93biBnZW5kZXIgYXQgdGhhdCBhZ2UuIFRoaXMgbWlnaHQgYmUgc29tZXRoaW5nIHdlIHdhbnQgdG8gZGlnIGludG8gYSBiaXQgbW9yZSwgYnV0IGZvciBub3csIGxldCdzIGZpbHRlciBvdXQgdGhlIGJpa2VycyB3aXRoIGEgZ2VuZGVyIG9mIGAwYC4gV2UgYWxzbyBmaWx0ZXJlZCBvdXQgYmlrZXJzIHdpdGggYW4gYWdlIG92ZXIgYDEwMGAgJm1kYXNoOyB0aGF0IHNlZW1zIGxpa2UgYW4gZXJyb3IgaW4gZGF0YSBjb2xsZWN0aW9uIGFzIHdlbGwuCgpGaW5hbGx5LCB3ZSBsYWJlbGVkIGFuZCB0aXRsZWQgb3VyIGdyYXBoIHVzaW5nIGBsYWJzKClgIGFuZCBgc2NhbGVfZmlsbF9kaXNjcmV0ZSgpYAoKYGBge3J9CiMgRmlsdGVyIGFuZCBsYWJlbCB5b3VyIGdyYXBoCmFnZV9jb3VudHMgPC0gYWdlX2NvdW50cyAlPiUKICBmaWx0ZXIoCiAgICBhZ2UgPCA4MCwgZ2VuZGVyID09IDEgfCBnZW5kZXIgPT0gMgogICkgCgphZ2VfY291bnRzICU+JQogIGdncGxvdCgKICAgIGFlcygKICAgICAgeCA9IGFnZSwKICAgICAgeSA9IG4sCiAgICAgIGZpbGwgPSBhcy5mYWN0b3IoZ2VuZGVyKQogICAgKQogICkgKwogIGdlb21fY29sKCkgKwogIGxhYnMoCiAgICB0aXRsZSA9ICJBdmVyYWdlIFNwZWVkIG9mIENpdGkgQmlrZSB1c2VycyBieSBhZ2UgKEphbnVhcnkgMjAyMCkiLAogICAgeCA9ICJBZ2UiLAogICAgeSA9ICJNZWFuIFNwZWVkIChtL3MpIgogICkgKwogIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKSArIAogIHNjYWxlX2ZpbGxfZGlzY3JldGUoCiAgICBuYW1lID0gIkdlbmRlciIsCiAgICBsYWJlbHMgPSBjKAogICAgICAiMSIgPSAiTWFsZSIsCiAgICAgICIyIiA9ICJGZW1hbGUiCiAgICApCiAgKQpgYGAKCiMjIyBGdXJ0aGVyIFdvcmsKCkdyZWF0IHdvcmshIFlvdSd2ZSBtYWRlIHNldmVyYWwgZ3JhcGhzIHRoYXQgc2hvdyBhIHJlYWwgZGlmZmVyZW5jZSBpbiB0aGUgd2F5IGRpZmZlcmVudCBncm91cHMgb2YgQ2l0aSBCaWtlIHVzZXJzIGJpa2UuIFRoaXMgY291bGQgYmUgYSB2YWx1YWJsZSBhc3NldCBpbiBoZWxwaW5nIENpdGkgQmlrZSB1bmRlcnN0YW5kIGhvdyB0byBtYWtlIGJpa2UgcmlkaW5nIHNhZmVyIGluIE5ldyBZb3JrLiBIb3dldmVyLCB0aGVyZSBpcyBzbyBtdWNoIG1vcmUgeW91IGNhbiBkbyB3aXRoIHRoaXMgZGF0YSEKClRvIGJlZ2luLCB0aGVyZSBhcmUgc29tZSBtYWpvciBmbGF3cyBpbiB0aGUgd2F5IHdlIGNhbGN1bGF0ZWQgdGhlIHNwZWVkLiBTcGVjaWZpY2FsbHksIHdlIG1hZGUgc29tZSBfaHVnZV8gYXNzdW1wdGlvbnMgd2hlbiBjYWxjdWxhdGluZyB0aGUgZGlzdGFuY2Ugb2YgZWFjaCBiaWtlIHJpZGUuIEluc3RlYWQgb2YgY2FsY3VsYXRpbmcgdGhlIHN0cmFpZ2h0IGxpbmUgZGlzdGFuY2UgdXNpbmcgdGhlIGdlb3NwaGVyZSBsaWJyYXJ5LCB3ZSBjb3VsZCB0YWtlIGFkdmFudGFnZSBvZiBhIHNlcnZpY2UgbGlrZSBHb29nbGUgTWFwJ3MgQVBJIHRvIGdldCBhIG1vcmUgYWNjdXJhdGUgbWVhc3VyZW1lbnQgb2YgZGlzdGFuY2UuIElmIHlvdSdyZSBpbnRlcmVzdGVkIGluIGxvb2sgbW9yZSBpbnRvIHRoaXMgcHJvYmxlbSwgaW52ZXN0aWdhdGUgZ2V0dGluZyBhIFtHb29nbGUgTWFwcyBBUEkga2V5XShodHRwczovL2RldmVsb3BlcnMuZ29vZ2xlLmNvbS9tYXBzL2RvY3VtZW50YXRpb24vZ2VvY29kaW5nL2dldC1hcGkta2V5KSBhbmQgdXNpbmcgYSBsaWJyYXJ5IGxpa2UgW2dtYXBzZGlzdGFuY2VdKGh0dHBzOi8vY3Jhbi5yLXByb2plY3Qub3JnL3dlYi9wYWNrYWdlcy9nbWFwc2Rpc3RhbmNlL2dtYXBzZGlzdGFuY2UucGRmKS4KCkFub3RoZXIgZ3JlYXQgd2F5IHRvIGV4dGVuZCB0aGlzIHByb2plY3QgaXMgdG8gaW52ZXN0aWdhdGUgb3RoZXIgZGF0YSB0aGF0IENpdGkgQmlrZSBtYWtlcyBhdmFpbGFibGUuIFdlIHVzZWQgZGF0YSBmb3VuZCBpbiB0aGUgX0NpdGkgQmlrZSBUcmlwIEhpc3Rvcmllc18gc2VjdGlvbiBvbiB0aGUgW1N5c3RlbSBEYXRhXShodHRwczovL3d3dy5jaXRpYmlrZW55Yy5jb20vc3lzdGVtLWRhdGEpIHBhZ2UuIE9uIHRoZSBTeXN0ZW0gRGF0YSBwYWdlLCB5b3UgY2FuIGZpbmQgZGlmZmVyZW50IGRhdGFzZXQsIGluY2x1ZGluZyBpbmZvcm1hdGlvbiBhYm91dCBtZW1iZXJzaGlwIGRhdGEgYW5kIHJlYWwgdGltZSBzdGF0aW9uIGRhdGEuIFlvdSBjb3VsZCB1c2UgdGhpcyByZWFsIHRpbWUgZGF0YSB0byB0cmFjayBob3cgdGhlIGZsb3cgb2YgYmlrZXMgY2hhbmdlcyBvdmVyIHRoZSBjb3Vyc2Ugb2YgdGhlIGRheS4gWW91IGNvdWxkIGludmVzdGlnYXRlIGhvdyB0aGUgd2VhdGhlciBpbXBhY3RzIG1lbWJlcnNoaXAuIFdlIHdvdWxkIGxvdmUgdG8gc2VlIGFueSBncmFwaHMgb3IgaW5zaWdodHMgeW91IHByb2R1Y2Uh